package sudoku.model;

import java.util.LinkedList;
import java.util.Observable;
import java.util.Observer;

import sudoku.view.IView;
import utils.Constants;
import utils.Pair;

/**
 * Modelo. maneja toda la logica del sudoku. Interactua con Board y con Time
 */
public class Model extends Observable
{

	private boolean	nameRequested_;
	private boolean	wasPressedStop_;
	private boolean	wasPressedSolve_;
	private boolean	wasAskedClue_;
	private float		nScore_;
	private int			nDifficulty_;
	private String	userNameTopTen_;
	private Time		time_;

	// tablero de sudoku
	private Board		board_;
	private TopTen	topTen_;

	public Model() {
		topTen_ = TopTen.getInstance();
		loadFileTopTen();
		time_ = Time.getIntance();
	}

	/**
	 * @param view
	 *          {@link IView} carga los observadores
	 */
	public void loadObserver(IView view)
	{
		this.addObserver((Observer) view);
		time_.addObserver((Observer) view);
	}

	/**
	 * @return {@link Boolean} True ssi se esta corriendo una partida
	 */
	public boolean isRunningGame()
	{
		return time_.isRunning();
	}

	/**
	 * Este metodo es llamado al completarse el sudoku (ya sea mediante una pista,
	 * mediante la funcion resolver o individualmente por parte del jugador).
	 * Corta el juego y realiza lo necesario en cada caso, de acuerdo al motivo por el que
	 * se llamó a este método.
	 */
	public void endGame()
	{
		if (wasPressedStop_) 
			time_.timeStop(); // se presiono stop o se termino el juego pidiendo una pista como ultimo caso para completar el juego
		else {
			// si termino el juego sin ayuda
			if (!wasAskedClue_ && !wasPressedSolve_) {
				time_.timeStop();// corta el tiempo
				nScore_ = 10000 / (board_.getMovementAmount()) + (time_.getFinalTime());// actualiza el puntaje
				nameRequested_ = true;// setea true a la variable pedir nombre
			} else if (wasPressedSolve_) {
				board_.solveBoard();
				wasAskedClue_ = true;
				this.setChanged();
				Pair<Integer, Object> par = new Pair<Integer, Object>(1,
						board_.getBoard());
				this.notifyObservers(par);
				time_.timeStop();
			}
		} 
			

	}


	
	/**
	 * @param difficulty
	 *          Inicia un nuevo juego.
	 */
	public void newGame(int difficulty)
	{ 
		wasAskedClue_ = false;
		wasPressedStop_ = false;
		nameRequested_ = false;
		wasPressedSolve_ = false;
		board_ = new Board(difficulty);
		this.nDifficulty_ = difficulty;
		this.notifyDraw();
		// dibuja el tablero y larga el contador de tiempo
		time_.timeGo();
	}

	/**
	 * Notifica que cambió, y les pasa como argumento el tablero inicial de juego 
	 */
	public void notifyDraw()
	{
		this.setChanged();
		Pair<Integer, Object> par = new Pair<Integer, Object>(Constants.DIBUJARMATRIZ, board_.getInitialBoard());
		this.notifyObservers(par);
	}
	
	/**
	 * @return true ssi el sudoku ha sido resuelto correctamente
	 */
	public boolean isSolvedSudoku()
	{
		return board_.isSolvedSudoku();
	}

	/**
	 * guarda un usuario en la lista de topTen (en caso de corresponder)
	 */
	public void saveInTopTen()
	{
		topTen_.insert(this.nDifficulty_, userNameTopTen_ + " - " + nScore_);
	}

	/**
	 * almacena en el archivo "TopTen.txt" toda la lista de topTen con los mejores
	 * jugadores. Este metodo es llamado solamente al salir del juego (de la aplicacion)
	 */
	public void saveChanges()
	{
		topTen_.setTopTen(Constants.FACIL);
		topTen_.setTopTen(Constants.MEDIANA);
		topTen_.setTopTen(Constants.DIFICIL);
	}

	/**
	 * carga las listas de topTen con cada uno de los archivos
	 */
	public void loadFileTopTen()
	{
		topTen_.getTopTen(Constants.FACIL);
		topTen_.getTopTen(Constants.MEDIANA);
		topTen_.getTopTen(Constants.DIFICIL);
	}

	/**
	 * @param difficulty
	 * @return lista de top ten de la dificultad pasada como parametro
	 */
	public LinkedList<String> getTopTen(int difficulty)
	{
		switch (difficulty)
		{
			case Constants.FACIL:
				return topTen_.getTopTenListEasy();
			case Constants.MEDIANA:
				return topTen_.getTopTenListMedium();
			case Constants.DIFICIL:
				return topTen_.getTopTenListHard(); 
			default: return null;
				
		}
	}

	/**
	 * * @return {@link Boolean} true ssi se necesita el nombre para guardar en la
	 * lista top-ten. (El controlador esta pendiente de esto)
	 */
	public boolean nameRequested()
	{
		return nameRequested_;
	}

	/**
	 * @param name
	 * {@link String} modifica el nombre de usuario topTen
	 */
	public void setUsernameTopTen(String name)
	{
		userNameTopTen_ = name;
	}

	/**
	 * @return cantidad de movimientos realizados por el usuario
	 */
	public int getMovementAmount()
	{
		return board_.getMovementAmount();
	}

	/**
	 * setea true a la variable presiono stop
	 */
	public void setTruePressedStop()
	{
		this.wasPressedStop_ = true;
	}

	/**
	 * @return true ssi se genero una pista, false en caso contrario (matriz
	 *         cargada correctamente)
	 */
	public boolean generateClue()
	{
		boolean generatedClue = false;
		if (time_.isRunning()) {
			generatedClue = board_.canGenerateClue();
		  if (generatedClue) {
				wasAskedClue_ = true;
				Pair<Integer, Integer> pos = new Pair<Integer, Integer>(
						board_.getRowClue(), board_.getColClue());
				Pair<Pair<Integer, Integer>, Integer> pista = new Pair<Pair<Integer, Integer>, Integer>(
						pos, board_.getGeneratedClue());
				Pair<Integer, Object> par = new Pair<Integer, Object>(
						Constants.DIBUJARPISTA, pista);
				setChanged();
				notifyObservers(par);
		  }
		}
		return generatedClue;
	}

	/**
	 * Restaura el tablero inicial
	 */
	public void restoreBoard()
	{
		board_.restoreBoard();
		Pair<Integer, int[][]> par = new Pair<Integer, int[][]>(
				Constants.BORRARJUEGO, board_.getBoard());
		setChanged();
		notifyObservers(par);
	}

	/**
	 * @param posI
	 *          {@link Integer}
	 * @param posJ
	 *          {@link Integer}
	 * @param number
	 *          {@link Integer} setea el numero en la posicion dada en el Board.
	 */
	public void setBoardPos(int posI, int posJ, int number)
	{
		board_.setBoardPos(posI, posJ, number);
	}
	
	/**
	 * @param posI
	 * @param posJ
	 * @return el elemento del tablero correspondiente a la posicion posI, posJ
	 */
	public int getBoardPos(int posI, int posJ)
	{
		return board_.getBoardPos(posI, posJ);
	}

	/**
	 * setea en true la variable wasPressedSolve_
	 */
	public void setTruePressedSolved()
	{
		wasPressedSolve_ = true;
	}
	
	/**
	 * @return true ssi el tablero no tiene espacios vacios 
	 */
	public boolean hasEmptySpaces()
	{
		boolean isEmpty = false;
		for (int i = 0; i < 9 && !isEmpty; ++i)
			for (int j = 0; j < 9  && !isEmpty; ++j)
			{
				isEmpty = board_.getBoardPos(i,j) == 0;
			}
		return isEmpty;
	}
	
}
